package org.wheel.plugins.toolkit.jsqlparse.helper;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import org.wheel.module.core.security.SqlSecurityUtils;
import org.wheel.plugins.toolkit.sql.core.GlobalConfig;
import org.wheel.plugins.toolkit.sql.enums.Dialect;
import org.wheel.plugins.toolkit.sql.enums.SqlModel;

import java.lang.reflect.Array;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ParamValueConvert {

    // 直接使用 LogFactory 创建日志对象，Hutool 会自动检测项目中的日志框架（优先级：Slf4j > Log4j > Logback > JDK Logging）
    private static final Log log = LogFactory.get();

    /**
     * 用于原生SQL参数替换，将值设置为JSqlParser表达式
     * @param val
     * @return
     */
    public static Expression convertToComparisonOperator(Object val) {
        if(val == null || (val instanceof String && val.toString().equalsIgnoreCase("null"))) {
            return new NullValue();
        }if (val instanceof String) {
            // 对字符串值进行SQL注入防护
            return new StringValue(SqlSecurityUtils.escapeSqlValue(val.toString()));
        } else if (val instanceof Integer || val instanceof Long || val instanceof Short) {
            return new LongValue(val.toString());
        } else if (val instanceof Double || val instanceof Float) {
            return new DoubleValue(val.toString());
        } else if (val instanceof Boolean bool) {
            if(GlobalConfig.getDialect() == Dialect.POSTGRESQL
                    || GlobalConfig.getDialect() == Dialect.SQLSERVER){
                return new StringValue(bool ? "TRUE" : "FALSE");
            }
            return new LongValue(bool ? 1 : 0);
        } else if (val instanceof Date) {
            return new StringValue(DateUtil.format((Date) val, DatePattern.NORM_DATETIME_PATTERN));
        } else if(val instanceof LocalDate){
            return new StringValue(((LocalDate) val).format(DatePattern.NORM_DATE_FORMATTER));
        } else if(val instanceof LocalDateTime value){
            return new StringValue(DateUtil.format(value, DatePattern.NORM_DATETIME_PATTERN));
        } if(val instanceof Instant){
            return new StringValue(DateUtil.format(LocalDateTime.from(Instant.ofEpochMilli(((Instant)val).toEpochMilli())), DatePattern.NORM_DATETIME_PATTERN));
        } else if(val instanceof Collection<?> collection){
            return new ExpressionList(collection.stream().map(ParamValueConvert::convertToComparisonOperator).toList());
        } else if(val.getClass().isArray()){
            return new ExpressionList(IntStream.range(0, Array.getLength(val)).mapToObj(i -> convertToComparisonOperator(Array.get(val, i))).toList());
        } else if(val instanceof Map<?,?> map){
            return new ExpressionList(map.values().stream().map(ParamValueConvert::convertToComparisonOperator).toList());
        }
        return null;
    }

    private static final Pattern NAMED_PARAM = Pattern.compile(":(\\w?\\d?\\.?\\w+)");
    /**
     * 入口：把 SQL（含 :pX）+ ParamBank 映射 替换成字面量
     * 专门将参数直接设置到SQL中，适用于 Direct 模式
     * @param sql 带 :propertiesName 的 SQL
     * @param paramMap 参数
     * @param dialect 方言
     * @return  SQL
     */
    public static String convertValuesIntoSql(String sql,
                                              Map<String, Object> paramMap,
                                              Dialect dialect) {
        if(MapUtil.isEmpty(paramMap)){
            return sql;
        }
//        log.debug("SQL: {}, param: {}", sql, paramMap);
//        return StrUtil.replace(sql, "(:)(\\w+)", m -> {
//            String key = m.group(2);
//            Object val = paramMap.get(key);
//            if (!paramMap.containsKey(key) && val == null) {
//                throw new IllegalArgumentException("参数未绑定: " + key);
//            }
//            return toLiteral(val, dialect);
//        });
        Matcher m = NAMED_PARAM.matcher(sql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            String key = m.group(1);
            Object val = paramMap.get(key);
            if (val == null && !paramMap.containsKey(key)) {
                throw new IllegalArgumentException("参数未绑定: " + key);
            }
            m.appendReplacement(sb, Matcher.quoteReplacement(toLiteral(val, dialect)));
        }
        m.appendTail(sb);
        return sb.toString();
    }
    /**
     * 入口：把 SQL（含 ?）+ ParamBank 映射 替换成字面量
     * 专门将参数直接设置到SQL中，适用于 Direct 模式
     * @param sql 带 ? 的 SQL
     * @param paramMap 参数
     * @param dialect 方言
     * @return  SQL
     */
    public static String convertValuesIntoSql(String sql,
                                              Object[] paramMap,
                                              Dialect dialect) {
        if(ArrayUtil.isEmpty(paramMap)){
            return sql;
        }
        // 1. 先把每个参数转成字面量
        Object[] literals = Arrays.stream(paramMap)
                .map(p -> toLiteral(p, dialect))
                .toArray();
        // 2. 格式化
        return StrUtil.format(sql.replace("?", "{}"), literals);
    }

    /* ========== 核心：Object → 方言字面量 ========== */
    public static String toLiteral(Object val, Dialect dialect) {
        // 因为存在 null 值, SQL中替换字面量,所以返回字符串"null"
        if(val == null){
            return "null";
        }
        /* ----- 字符串 ----- */
        if (val instanceof String s) {
//            return "'" + s + "'";
            return SqlSecurityUtils.escapeSqlValue(s); // 已含引号
        }
        /* ----- 布尔 ----- */
        if (val instanceof Boolean b) {
            return dialect == Dialect.POSTGRESQL ? (b ? "TRUE" : "FALSE")
                    : (b ? "1" : "0");
        }
        /* ----- 数值 ----- */
        if (val instanceof Number) {
            return val.toString();
        }
        /* ----- 时间类型 → 统一转 Timestamp 字符串 ----- */
        if (val instanceof LocalDateTime ldt) {
            return timestampLiteral(Timestamp.valueOf(ldt), dialect);
        }
        if (val instanceof LocalDate ld) {
            return dateLiteral(DateUtil.date(ld), dialect);
        }
        if (val instanceof LocalTime lt) {
            return timeLiteral(Time.valueOf(lt), dialect);
        }
        if (val instanceof ZonedDateTime zdt) {
            return timestampLiteral(Timestamp.from(zdt.toInstant()), dialect);
        }
        if (val instanceof Instant ins) {
            return timestampLiteral(Timestamp.from(ins), dialect);
        }
        if (val instanceof Timestamp ts) {
            return timestampLiteral(ts, dialect);
        }
        if (val instanceof Date d) {
            return dateLiteral(d, dialect);
        }
        if (val instanceof Time t) {
            return timeLiteral(t, dialect);
        }
        /* ----- 集合 → IN ( ... ) 展开 ----- */
        if (val instanceof Collection<?> c) {
            if (CollUtil.isEmpty(c)) {
                throw new IllegalArgumentException("IN 列表不能为空");
            }
            return c.stream()
                    .map(v -> toLiteral(v, dialect))
                    .collect(Collectors.joining(", "));
//                    .collect(Collectors.joining(", ", "(", ")"));  // 前后添加括号
        }
        if (val instanceof Object[] arr) {
            return Arrays.stream(arr)
                    .map(v -> toLiteral(v, dialect))
                    .collect(Collectors.joining(", "));
//                    .collect(Collectors.joining(", ", "(", ")"));  // 前后添加括号
        }
        /* ----- 默认 toString 兜底 ----- */
        return "'" + val.toString() + "'";
    }

    /* ========== 方言日期时间格式 ========== */
    private static String timestampLiteral(Timestamp ts, Dialect dialect) {
        String iso = ts.toString(); // yyyy-MM-dd HH:mm:ss.SSS
        return dialect == Dialect.POSTGRESQL ?
                "'" + iso + "'" : "TIMESTAMP '" + iso + "'";
    }

    private static String dateLiteral(Date d, Dialect dialect) {
        String iso = d.toString(); // yyyy-MM-dd
        return dialect == Dialect.POSTGRESQL ?
                "'" + iso + "'" : "DATE '" + iso + "'";
    }

    private static String timeLiteral(Time t, Dialect dialect) {
        String iso = t.toString(); // HH:mm:ss
        return dialect == Dialect.POSTGRESQL ?
                "'" + iso + "'" : "TIME '" + iso + "'";
    }

//    /**
//     * 将值转换为JSQLParser表达式（用于DIRECT_SQL模式）
//     */
//    private static Expression convertToExpression(Object value) {
//        if (value instanceof String || value instanceof Character || value instanceof Byte) {
//            return new StringValue("'" + SqlSecurityUtils.escapeSqlValue((String) value) + "'");
//        } else if (value instanceof Integer || value instanceof Long || value instanceof Short) {
//            return new LongValue(String.valueOf(value));
//        } else if (value instanceof Double || value instanceof Float) {
//            return new DoubleValue(String.valueOf(value));
//        } else if (value instanceof Boolean val) {
//            return new BooleanValue(val);
//        } else if (value instanceof Date val) {
//            return new DateValue(DateUtil.formatDate(val));
//        } else if (value instanceof LocalDate val) {
//            return new DateValue(val.format(DatePattern.NORM_DATE_FORMATTER));
//        } else if (value instanceof LocalDateTime val) {
//            return new TimestampValue(val.format(DatePattern.NORM_DATETIME_MS_FORMATTER));
//        } else {
//            return new StringValue("'" + SqlSecurityUtils.escapeSqlValue(String.valueOf(value)) + "'");
//        }
//    }
    /**
     * 查找map中key的索引
     * @param map 待查找的map
     * @param keyToFind 要查找的key
     * @return key的索引，如果没有找到，则返回-1
     * @param <K> key的类型
     * @param <V> value的类型
     */
    public static <K, V> int findIndex(LinkedHashMap<K, V> map, K keyToFind) {
        int[] index = {0}; // 使用数组以便在Lambda表达式中修改

        // 使用Stream API找到key的索引
        Optional<Integer> keyIndex = map.entrySet().stream()
                .peek(entry -> index[0]++)
                .filter(entry -> entry.getKey().equals(keyToFind))
                .map(entry -> index[0])
                .findFirst();

        return keyIndex.orElse(-1);
    }

    /**
     * 替换命名参数
     * @param originalSql 原始SQL
     * @param sqlModel SQL模型
     * @param paramValuesMap 参数值映射，正常的 SqlModel.PROPERTY_NAME 模式的参数。参数样例：
     * <pre>{@code
     * {
     *     "key_1": "XXXXXXXXXXXX",
     *     "key_2": ["aaaaaaaaaa", "bbbbbbbb"]
     * }
     * }</pre>
     * @return 替换后的SQL
     */
    public static String replaceNamedParameters(String originalSql, SqlModel sqlModel, LinkedHashMap<String, Object> paramValuesMap) {
        if(originalSql == null || SqlModel.PROPERTY_NAME.equals(sqlModel)){
            return originalSql;
        }
        return paramValuesMap.keySet().stream()
                .reduce(originalSql, (sql, key) -> {
                    String regex = Pattern.quote(new JdbcNamedParameter(key).toString());
                    String replacement = null;
                    if(sqlModel.equals(SqlModel.NUMBER)){
//                        replacement = String.format(sqlModel.getSqlFormat(), findIndex(paramValuesMap, key));
                        replacement = getValuePositions(paramValuesMap, key).stream()
                                .map(index->String.format(sqlModel.getSqlFormat(), index))
                                .collect(Collectors.joining(", "));
                    }else if(sqlModel.equals(SqlModel.MYBATIS)){
                        replacement = String.format(sqlModel.getSqlFormat(), key);
                    } else if (sqlModel.equals(SqlModel.NATIVE)){
//                        replacement = sqlModel.getSqlFormat();
                        replacement = getValuePositions(paramValuesMap, key).stream()
                                .map(index-> sqlModel.getSqlFormat())
                                .collect(Collectors.joining(", "));

                    }else if (sqlModel.equals(SqlModel.DIRECT_SQL)){
                        replacement = convertValuesToSqlString(paramValuesMap.get(key));
                    }

                    assert replacement != null;
                    return sql.replaceAll(regex, Matcher.quoteReplacement(replacement));

                }, (s1, s2) -> s2); // No parallel processing, so this part is not used
    }

    /**
     * 获取参数值在SQL中的位置。
     * 从参数对象 paramValuesMap，通过 key，得到参数值从第几个数计起，例如 key 在 paramValuesMap 中第 3 个位置，但是值有3个，所以返回 2，3，4
     * @param paramValuesMap 参数对象
     * @param targetKey 目标key
     * @return 参数值在SQL中的位置
     */
    public static List<Integer> getValuePositions(LinkedHashMap<String, Object> paramValuesMap, String targetKey) {
        // 1. 检查key是否存在
        if (!paramValuesMap.containsKey(targetKey)) {
            throw new IllegalArgumentException("Key not found in map: " + targetKey);
        }

        // 2. 计算前面所有key的总元素数（即起始位置）
        int startPosition = 1;
        for (Map.Entry<String, Object> entry : paramValuesMap.entrySet()) {
            if (entry.getKey().equals(targetKey)) {
                break;
            }
            startPosition += getElementCount(entry.getValue());
        }

        // 3. 获取当前key的值并计算位置范围
        Object value = paramValuesMap.get(targetKey);
        int elementCount = getElementCount(value);

        // 4. 生成位置序列
        return IntStream.range(startPosition, startPosition + elementCount)
                .boxed()
                .collect(Collectors.toList());
    }

    /**
     * 辅助方法：计算不同值类型的元素数量。
     * 获取集合、数组、Map等类型的元素数量
     * @param value 待计算元素数量的对象
     * @return 元素数量
     */
    private static int getElementCount(Object value) {
        if (value instanceof Collection<?> collection) {
            return collection.stream().map(ParamValueConvert::getElementCount).mapToInt(Integer::intValue).sum();
        } else if (value instanceof Map<?, ?> map) {
            return map.values().stream().map(ParamValueConvert::getElementCount).mapToInt(Integer::intValue).sum();
        } else if (value instanceof Array) {
            return Array.getLength(value);
        }
        // 非集合类型视为单个元素
        return 1;
    }

    /**
     * 转换值为SQL字符串
     * @param val
     * @return
     */
    public static String convertValuesToSqlString(Object val) {
        if (val == null){
            return "null";
        } else if (val instanceof String value) {
            // 对字符串值进行SQL注入防护
            return "'" + SqlSecurityUtils.escapeSqlValue(val.toString()) + "'";
        } else if (val instanceof Integer) {
            return val.toString();
        } else if (val instanceof Long) {
            return val.toString();
        } else if (val instanceof Double) {
            return val.toString();
        } else if (val instanceof Float) {
            return val.toString();
        } else if (val instanceof Boolean bool) {
            return bool ? "1" : "0";
        } else if (val instanceof Date) {
            return "'" + DateUtil.format((Date) val, DatePattern.NORM_DATETIME_PATTERN) + "'";
        } else if(val instanceof LocalDate){
            return "'" + ((LocalDate) val).format(DatePattern.NORM_DATE_FORMATTER) + "'";
        } else if(val instanceof LocalDateTime){
            return "'" + DateUtil.format((LocalDateTime) val, DatePattern.NORM_DATETIME_PATTERN) + "'";
        } else if (val instanceof Collection<?>  collection) {
            return collection.stream()
                    .map(ParamValueConvert::convertValuesToSqlString)
                    .reduce((s1, s2) -> s1 + "," + s2)
                    .orElse("");
        } else if(val instanceof Map<?,?> map){
            return map.values().stream()
                    .map(ParamValueConvert::convertValuesToSqlString)
                    .reduce((s1, s2) -> s1 + "," + s2)
                    .orElse("");
        } else {
            return "";
        }
    }

    /**
     * 转换值
     * @param type 数据类型
     * @param val 值
     * @return 转换后对应的类型值
     */
    public static Object convertValue(Class<?> type, String val){
        return switch (type.getSimpleName()) {
            case "String" -> val;
            case "Integer" -> Integer.parseInt(val);
            case "Long" -> Long.parseLong(val);
            case "Double" -> Double.parseDouble(val);
            case "Float" -> Float.parseFloat(val);
            case "Boolean" -> Boolean.parseBoolean(val);
            case "Date" -> DateUtil.parse(val);
            case "LocalDate" -> DateUtil.parse(val).toLocalDateTime().toLocalDate();
            case "LocalDateTime" -> DateUtil.parse(val).toLocalDateTime();
            default -> Convert.convert(type, val);
        };
    }

    /**
     * JSON 对象 扁平化
     * @param json
     * @param prefix
     * @return
     * @throws JSONException
     */
    public static JSONObject flattenJson(JSONObject json, String prefix) throws JSONException {
        JSONObject flatJson = new JSONObject();

        for (Iterator it = json.iterator(); it.hasNext(); ) {
            String key = (String) it.next();
            Object value = json.get(key);

            if (value instanceof JSONObject) {
                JSONObject nestedJson = flattenJson((JSONObject) value, prefix + key + ".");
                for (Iterator iter = nestedJson.iterator(); iter.hasNext(); ) {
                    String nestedKey = (String) iter.next();
//                    flatJson.put(prefix + nestedKey, nestedJson.get(nestedKey).getClass().getSimpleName());
                    flatJson.put(prefix + nestedKey, nestedJson.get(nestedKey));
                }
            } else if (value instanceof JSONArray) {
                JSONArray array = (JSONArray) value;
//                for (int i = 0; i < array.length(); i++) {
                for (int i = 0; i < 1; i++) {
                    if (array.get(i) instanceof JSONObject) {
                        JSONObject arrayItem = flattenJson(array.getJSONObject(i), prefix + key + ".");
                        for (Iterator iter = arrayItem.iterator(); iter.hasNext(); ) {
                            String itemKey = (String) iter.next();
//                            flatJson.put(itemKey, arrayItem.get(itemKey).getClass().getSimpleName());
                            flatJson.put(itemKey, arrayItem.get(itemKey));
                        }
                    } else {
//                        flatJson.put(prefix + key + "." + i, array.get(i).getClass().getSimpleName());
                        flatJson.put(prefix + key + "." + i, array.get(i));
                    }
                }
            } else {
                flatJson.put(prefix + key, value);
            }
        }

        return flatJson;
    }
}
